/*
 * ByteBufferPool.java    0.0.1    17/set/2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */ 
package org.mentalsmash.tazio.net.cs.server.impl;

import java.nio.ByteBuffer;
import java.util.NoSuchElementException;

import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of a BufferPool. A client on this class can obtain a direct buffer
 * from the pool calling the <code>borrowObject</code> method: if the pool has unused buffers
 * it returns one to the caller. 
 * The coller <strong>must pay attention</strong> to return the buffer after it has finished using it
 * by calling the <code>returnObject()</code> method. That is very important especially if
 * the policy of the pool is to create new buffers indefinitely upon clients' requests,
 * if there are no available idle buffers to return. 
 * Therefore if the clients never return the borrowed buffers, the number of active buffers may
 * grow without limit. (that's the default policy)
 * <p>
 * You can also use a fixed-size pool by calling the appropriate
 * {@link #ByteBufferPool(int,int,int,boolean) constructor} and setting the <code>fixedPoolSize</code>
 * parameter to true. In that case the pool will not grow, and unsatisfiable requests will block until
 * a buffer is returned to the pool.
 * </p>	.
 * @see ByteBufferFactory
 * @version    0.0.1    17/set/2009    
 * @author     Andrea Reale <andrea.reale@gmail.com>
 *
 */
public class ByteBufferPool implements BufferPool {
	/*
	 * This implementation wraps an
	 * org.apache.commons.pool.impl.GenericObjectPool for pool services
	 * 
	 */
	
	/* Sets the default action to perform when no idle buffers are available, to make the pool grow*/
	private final static byte WHEN_EXHAUSTED_ACTION = GenericObjectPool.WHEN_EXHAUSTED_GROW;
	
	private final static Logger log = LoggerFactory.getLogger(ByteBufferPool.class);
	
	private final GenericObjectPool pool; //wrapped GenericObjectPool
	private final ByteBufferFactory bufferFactory; //factory used to build buffers in the pool
	
	
	/**
	 * Create a new <code>ByteBufferPoolImpl</code> with defaults parameters
	 */
	public ByteBufferPool() {
		bufferFactory = new ByteBufferFactory();
		pool = new GenericObjectPool(bufferFactory);
		pool.setWhenExhaustedAction(WHEN_EXHAUSTED_ACTION);
	}
	
	/**
	 * Create a new <code>ByteBufferPoolImpl</code> using the specified values
	 * @param maxActive maximum number of instances in the pool. It counts both active and idle instances.
	 * @param maxIdle maximum number of idle instances in the pool. Whenever a buffer is returned an the 
	 *                pool already has <code>maxIdle</code> idle instances, the returned instance
	 *                is discarded and eventually garbage-collected.
	 */
	public ByteBufferPool(int maxActive, int maxIdle){	
		this(maxActive,maxIdle,ByteBufferFactory.DEFAULT_CAPACITY,false);
	}
	
	/**
	 * Create a new <code>ByteBufferPoolImpl</code> using the specified values
	 * @param maxActive maximum number of instances in the pool. It counts both active and idle instances.
	 *                  
	 * @param maxIdle maximum number of idle instances in the pool. Whenever a buffer is returned an the 
	 *                pool already has <code>maxIdle</code> idle instances, the returned instance
	 *                is discarded and eventually garbage-collected.
	 * @param bufferSize the size of the buffers that the bufferFactory will build. It must be in correct
	 *                   allowed range for the {@link ByteBufferFactory}
	 * @param fixedPoolSize Set it to ture if you want the number of buffers in the pool to be fixed. 
	 *                      In that case the pool will allocate a maximum of <code>maxActive</code> 
	 *                      instances. 
	 * @see ByteBufferFactory
	 */
	public ByteBufferPool(int maxActive, int maxIdle, int bufferSize, boolean fixedPoolSize) {
		bufferFactory = new ByteBufferFactory(bufferSize);
		GenericObjectPool.Config config = new GenericObjectPool.Config();
		config.maxActive = maxActive;
		config.maxIdle = maxIdle;
		
		config.whenExhaustedAction = 
			fixedPoolSize ? GenericObjectPool.WHEN_EXHAUSTED_BLOCK : GenericObjectPool.WHEN_EXHAUSTED_GROW;
		
		pool = new GenericObjectPool(bufferFactory,config);
		
	}
	
	
    /**
     * <p>Borrows an object from the pool.</p>
     * 
     * <p>If there is an idle instance available in the pool, then either the most-recently returned
     * (if {@link GenericObjectPool#getLifo() lifo} == true) or "oldest" (lifo == false) instance sitting idle in the pool
     * will be activated and returned.  If activation fails, or {@link GenericObjectPool#getTestOnBorrow() testOnBorrow} is set
     * to true and validation fails, the instance is destroyed and the next available instance is examined.
     * This continues until either a valid instance is returned or there are no more idle instances available.</p>
     * 
     * <p>If there are no idle instances available in the pool, behavior depends on the {@link GenericObjectPool#getMaxActive() maxActive}
     * and (if applicable) {@link GenericObjectPool#getWhenExhaustedAction() whenExhaustedAction} and {@link GenericObjectPool#getMaxWait() maxWait}
     * properties. If the number of instances checked out from the pool is less than <code>maxActive,</code> a new
     * instance is created, activated and (if applicable) validated and returned to the caller.</p>
     * 
     * <p>If the pool is exhausted (no available idle instances and no capacity to create new ones),
     * this method will either block ({@link GenericObjectPool#WHEN_EXHAUSTED_BLOCK}), throw a <code>NoSuchElementException</code>
     * ({@link GenericObjectPool#WHEN_EXHAUSTED_FAIL}), or grow ({@link GenericObjectPool#WHEN_EXHAUSTED_GROW} - ignoring maxActive).
     * The length of time that this method will block when <code>whenExhaustedAction == WHEN_EXHAUSTED_BLOCK</code>
     * is determined by the {@link GenericObjectPool#getMaxWait() maxWait} property.</p>
     * 
     * <p>When the pool is exhausted, multiple calling threads may be simultaneously blocked waiting for instances
     * to become available.  As of pool 1.5, a "fairness" algorithm has been implemented to ensure that threads receive
     * available instances in request arrival order.</p>
     * 
     * @return object instance
     * @throws NoSuchElementException if an instance cannot be returned
     */
	@Override
	public ByteBuffer borrowObject() throws Exception, NoSuchElementException,
			IllegalStateException {
		
		ByteBuffer buffer = (ByteBuffer)pool.borrowObject();
		log.debug("A new ByteBuffer has been borrowed");
		return buffer;

	}
	



    /**
     * <p>Returns an object instance to the pool.</p>
     * 
     * <p>If {@link #getMaxIdle() maxIdle} is set to a positive value and the number of idle instances
     * has reached this value, the returning instance is destroyed.</p>
     * 
     * <p>If {@link GenericObjectPool#getTestOnReturn() testOnReturn} == true, the returning instance is validated before being returned
     * to the idle instance pool.  In this case, if validation fails, the instance is destroyed.</p>
     * 
     * <p><strong>Note: </strong> There is no guard to prevent an object
     * being returned to the pool multiple times. Clients are expected to
     * discard references to returned objects and ensure that an object is not
     * returned to the pool multiple times in sequence (i.e., without being
     * borrowed again between returns). Violating this contract will result in
     * the same object appearing multiple times in the pool and pool counters
     * (numActive, numIdle) returning incorrect values.</p>
     * 
     * @param obj instance to return to the pool
     */
	@Override
	public void returnObject(ByteBuffer obj) throws Exception {
		pool.returnObject(obj);
		log.debug("A ByteBuffer has been returned to the pool");
		
	}


	/**
	 * Throws an UnsupportedOperationException
	 */
	@Override
	public void addObject() throws Exception, IllegalStateException,
			UnsupportedOperationException {
		throw new UnsupportedOperationException("Cannot manually add buffers to the pool");
		
	}

	/**
	 * Throws an UnsupportedOperationException
	 */
	@Override
	public void clear() throws Exception, UnsupportedOperationException {
		throw new UnsupportedOperationException("Cannot manually clear the pool");
		
	}

    /**
     * Closes the pool.  Once the pool is closed, {@link #borrowObject()}
     * will fail with IllegalStateException, but {@link #returnObject(Object)} and
     * {@link #invalidateObject(Object)} will continue to work. This method does not
     * {@link #clear()} the pool. The method is idempotent - that is, it is OK to call it on a closed
     * pool. 
     * 
     * @throws Exception
     */
	@Override
	public void close() throws Exception {
		pool.close();
		
	}

    /**
     * Return the number of instances currently borrowed from this pool.
     *
     * @return the number of instances currently borrowed from this pool
     */
	@Override
	public int getNumActive() throws UnsupportedOperationException {
		return pool.getNumActive();
	}

    /**
     * Return the number of instances currently idle in this pool.
     *
     * @return the number of instances currently idle in this pool
     */
	@Override
	public int getNumIdle() throws UnsupportedOperationException {

		return pool.getNumIdle();
	}
	
	/**
	 * Returns the cap on the number of "idle" instances in the pool.
	 * @return  the cap on the number of "idle" instances in the pool.
	 */
	public int getMaxIdle() {
		return pool.getMaxIdle();
	}


	
    /**
     * <p>Invalidates the given object instance.  Decrements the active count
     * and destroys the instance.</p>
     * 
     * @param obj instance to invalidate
     * @throws Exception if an exception occurs destroying the object
     */
	@Override
	public void invalidateObject(Object obj) throws Exception {
		pool.invalidateObject(obj);
		
	}

    /**
     * <p>Returns an object instance to the pool.</p>
     * 
     * <p>If {@link #getMaxIdle() maxIdle} is set to a positive value and the number of idle instances
     * has reached this value, the returning instance is destroyed.</p>
     * 
     * <p>If {@link GenericObjectPool#getTestOnReturn() testOnReturn} == true, the returning instance is validated before being returned
     * to the idle instance pool.  In this case, if validation fails, the instance is destroyed.</p>
     * 
     * <p><strong>Note: </strong> There is no guard to prevent an object
     * being returned to the pool multiple times. Clients are expected to
     * discard references to returned objects and ensure that an object is not
     * returned to the pool multiple times in sequence (i.e., without being
     * borrowed again between returns). Violating this contract will result in
     * the same object appearing multiple times in the pool and pool counters
     * (numActive, numIdle) returning incorrect values.</p>
     * 
     * @param obj instance to return to the pool
     */
	@Override
	public void returnObject(Object obj) throws Exception {
		if(!(obj instanceof ByteBuffer)) {
			throw new IllegalArgumentException("Cannot return an object that is not an instance" +
					"of ByteBuffer");
		}
		
		this.returnObject((ByteBuffer)obj);
		
	}


	/**
	 * Throws an UnsupportedOperationException
	 */
	@Override
	public void setFactory(PoolableObjectFactory factory)
			throws IllegalStateException, UnsupportedOperationException {
		
		throw new UnsupportedOperationException("Cannot change the object factory after" +
				"initialization");
		
	}

}
